// Copyright 2019 The rhyme_lph Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// N [author] rhyme_lph
// W [email]  rhymelph@gmail.com
// p [github] https://github.com/rhymelph

import 'dart:io';

import 'package:base_plugin/base_plugin.dart';
import 'package:finance_app/generated/l10n.dart';
import 'package:finance_app/src/common/config.dart';
import 'package:finance_app/src/common/prevent_repeated_event.dart';
import 'package:finance_app/src/entities/assistant_suggest_data_entity.dart';
import 'package:finance_app/src/entities/chat_room_entity.dart';
import 'package:finance_app/src/entities/chat_room_message_entity.dart';
import 'package:finance_app/src/http_service/http_assistant_service.dart';
import 'package:finance_app/src/mixin/chat_room_action_mixin.dart';
import 'package:finance_app/src/mixin/check_plan_mixin.dart';
import 'package:finance_app/src/router_gen/router_gen_providers.dart';
import 'package:finance_app/src/utils/date_time_utils.dart';
import 'package:finance_app/src/websocket/socket_request.dart';
import 'package:finance_app/src/websocket/web_socket.dart';
import 'package:finance_app/src/widgets/bottom_sheet/photo_preview_bottom_sheet.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;

import 'package:pull_to_refresh/pull_to_refresh.dart';

class ChatRoomProvider extends BaseProvider<ChatRoomEntity>
    with WidgetsBindingObserver, ChatroomActionMixin, CheckPlanMixin {
  TextEditingController inputController = TextEditingController();
  FocusNode inputFocusNode = FocusNode();
  List<ChatRoomMessageEntity> chatMessages = [];
  List<ChatRoomMessageGroupEntity> chatGroups = [];
  PreventRepeatedEvent repeatedEvent;

  ScrollController scrollController = ScrollController();
  WebSocketProvide _provide;
  Completer<void> _completer = Completer();
  StreamSubscription _subMsg;
  StreamSubscription _subSocketId;

  String entryPoint;

  RefreshController refreshController = RefreshController();

  List<AssistantSuggestDataEntity> suggestList = [];

  bool isShowVoice = false;

  @override
  FutureOr<ChatRoomEntity> transformData(ChatRoomEntity d, bool isCache) async {
    await _loadHistoryMessage(d.lastReadId);
    _completer.complete(null);
    return super.transformData(d, isCache);
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    repeatedEvent = PreventRepeatedEvent();
    repeatedEvent.addEventListener(_handleInput);
    inputController.addListener(_handleSearchInput);
    initWebSocket();
    Config.refreshMessageCount();
  }

  void _handleSearchInput() {
    repeatedEvent.sendEvent(inputController.text);
  }

  void _handleInput(data) {
    if (data.length < 3 || isShowVoice == true) {
      if (suggestList.length > 0) {
        suggestList.clear();
        update();
      }
      return;
    }
    post(HttpAssistantService.suggest(queryText: data), onSuccess: (result) {
      if (inputController.text == data) {
        suggestList =
            getEntityFromResult<List<AssistantSuggestDataEntity>>(result);
        update();
      }
    }, onError:
        (int statusCode, String msg, Object error, StackTrace stackTrace) {
      suggestList.clear();
      update();
    });
  }

  @override
  void dispose() {
    super.dispose();
    WebSocketProvide.closeWebSocket();
    repeatedEvent.dispose();
    _subMsg?.cancel();
    _subSocketId?.cancel();
    scrollController.dispose();
    WidgetsBinding.instance.removeObserver(this);
  }

  double keyboardHeight = 0.0;

  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      obtainContext?.call((context) {
        //update
        if (MediaQuery.of(context).viewInsets.bottom > 0) {
          keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
          print(keyboardHeight);
        }
        if (scrollController.position.maxScrollExtent == 0) return;
        final keyboardPosition = MediaQuery.of(context).viewInsets.bottom >= 0
            ? scrollController.position.pixels +
                MediaQuery.of(context).viewInsets.bottom
            : scrollController.position.pixels - keyboardHeight;

        final movePositions = math.min(
            scrollController.position.maxScrollExtent, keyboardPosition);

        scrollController.jumpTo(movePositions);
      });
    });
  }

  @override
  Future get dataFuture => HttpAssistantService.chatRoomAssistant();

  void onVoice() {
    if (isShowVoice == true) {
      isShowVoice = false;
      update();
      obtainContext?.call((context) {
        WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
          inputFocusNode.requestFocus();
        });
      });
    } else {
      isShowVoice = true;
      obtainContext?.call((context) {
        inputFocusNode.unfocus();
      });
    }
  }

  void onSend([int questionId]) async {
    suggestList.clear();
    update();
    final inputData = inputController.text;
    if (inputData.isEmpty) return;
    await Future.delayed(Duration(milliseconds: 100));
    inputController.clear();
    addChatRoomMessageAndUpdate([obtainText(inputData, questionId)]);
  }

  Future<void> _loadHistoryMessage(int lastReadId) async {
    await post(HttpAssistantService.currentMessages(entryPoint: entryPoint),
        autoHandler: AutoHandler.Toast, onSuccess: (result) {
      chatMessages = getEntityFromResult<List<ChatRoomMessageEntity>>(result);
      chatGroups = ChatRoomMessageGroupEntity.formatMessage(chatMessages);
      update();
    });
  }

  void addChatRoomMessage(List<ChatRoomMessageEntity> messages,
      [bool isLoadMore = false]) {
    if (isLoadMore == true) {
      chatMessages.insertAll(0, messages);
    } else {
      chatMessages.addAll(messages);
    }
    chatGroups = ChatRoomMessageGroupEntity.formatMessage(chatMessages);
    update();
  }

  ChatRoomMessageEntity obtainText(String text, [int questionId]) {
    //添加了一条，应该加一
    int index = chatMessages.length;
    post(
        HttpAssistantService.sendMessage(message: text, questionId: questionId),
        autoHandler: AutoHandler.Toast, onSuccess: (result) {
      final msgResult = getEntityFromResult<ChatRoomMessageEntity>(result);
      chatMessages.removeAt(index);
      // chatMessages.insert(index, msgResult);
      formatChatRoomMessageAndUpdate(chatMessages);
    });
    return ChatRoomMessageEntity()
      ..name = Config.userProfile.nickname
      ..content = text
      ..type = 'text'
      ..userId = Config.userProfile.id
      ..avatar = Config.userProfile.avatar
      ..createdAt =
          DateTimeUtils.formatDateTime(DateTime.now(), 'yyyy-MM-dd HH:mm:ss');
  }

  void initWebSocket() async {
    await _completer.future;
    _provide = await WebSocketProvide.getInstance();
    // SocketRequest.subUserChat(channel: 'chatroom.${data.id}');
    _subMsg = _provide.onListenerChatMessage((event) {
      addChatRoomMessageAndUpdate([event]);
    });
    _subSocketId = _provide.onListenerSocketId((event) {
      _loadPrivateChannelAuth(event);
    });
  }

  void onSelectAddFile(String value) async {
    String path;
    if (value == S.current.camera) {
      path = await Routes.navigateTo(RouterGenProviders.ocrCameraPage, params: {
        'isSelect': true,
      });
    } else {
      path =
          await Routes.navigateTo(RouterGenProviders.selectPhotoPage, params: {
        'isSelect': true,
      });
    }
    if (path != null) {
      onShowPreview(path, value);
    }
  }

  void onShowPreview(String path, String value) {
    obtainContext?.call((context) async {
      showPhotoPreviewBottomSheet(context, File(path), null,
          chooseAgainText: S.current.choose_again,
          onChooseAgain: value != S.current.camera
              ? () async {
                  obtainContext?.call((context) {
                    Navigator.of(context).pop();
                  });
                  onSelectAddFile(value);
                }
              : null, onCrop: () async {
        final result = await Routes.navigateTo(RouterGenProviders.editPhotoPage,
            params: {
              'filepath': path,
              'isFromPhoto': true,
            },
            replace: true);
        if (result != null) {
          onShowPreview(result, value);
        }
      }, onAttach: () {
        obtainContext?.call((context) {
          Navigator.of(context).pop();
        });
        addChatRoomMessageAndUpdate([onSendFile(path)]);
      },
          attachName: S.current.upload,
          onRetakePhoto: value != S.current.camera
              ? null
              : () {
                  obtainContext?.call((context) {
                    Navigator.of(context).pop();
                  });
                  onSelectAddFile(value);
                });
    });
  }

  ChatRoomMessageEntity onSendFile(String path) {
    int index = chatMessages.length;
    post(HttpAssistantService.sendImage(path), autoHandler: AutoHandler.Toast,
        onSuccess: (result) {
      final msgResult = getEntityFromResult<ChatRoomMessageEntity>(result);
      chatMessages.removeAt(index);
      chatMessages.insert(index, msgResult);
      formatChatRoomMessageAndUpdate(chatMessages);
    });
    return ChatRoomMessageEntity()
      ..name = Config.userProfile.nickname
      ..content = path
      ..type = 'image'
      ..userId = Config.userProfile.id
      ..avatar = Config.userProfile.avatar
      ..createdAt =
          DateTimeUtils.formatDateTime(DateTime.now(), 'yyyy-MM-dd HH:mm:ss');
  }

  void addChatRoomMessageAndUpdate(List<ChatRoomMessageEntity> messages) {
    addChatRoomMessage(messages);
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      if (scrollController.hasClients) {
        scrollController.animateTo(scrollController.position.maxScrollExtent,
            duration: Duration(milliseconds: 300), curve: Curves.easeIn);
      }
    });
  }

  void addChatRoomMessageAndKeyOffset(List<ChatRoomMessageEntity> messages) {
    final lastPosition = scrollController.position.extentAfter;

    addChatRoomMessage(messages, true);
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      if (scrollController.hasClients) {
        final currentPosition = scrollController.position.extentAfter;
        if (currentPosition > 0 && currentPosition > lastPosition) {
          scrollController.jumpTo(currentPosition - lastPosition);
        }
      }
    });
  }

  void formatChatRoomMessageAndUpdate(List<ChatRoomMessageEntity> messages) {
    chatGroups = ChatRoomMessageGroupEntity.formatMessage(messages);
    update();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      scrollController.animateTo(scrollController.position.maxScrollExtent,
          duration: Duration(milliseconds: 300), curve: Curves.easeIn);
    });
  }

  void _loadPrivateChannelAuth(String event) {
    HttpAssistantService.broadcastingAuth(
            socketId: event, channelName: 'private-chatroom.${data.id}')
        .then((value) {
      SocketRequest.subUserChat(
          auth: value.data['auth'], channel: 'private-chatroom.${data.id}');
    }, onError: (e) {
      showToast(
          S.current.private_chatroom_auth_errorplease_check_network_connect);
    });
  }

  void onLoadMore() {
    if (chatMessages.length == 0) return;
    post(
        HttpAssistantService.historyMessages(
            messageId: chatMessages.first.messageId.toString(),
            entryPoint: entryPoint),
        autoHandler: AutoHandler.Toast, onSuccess: (result) {
      List<ChatRoomMessageEntity> history =
          getEntityFromResult<List<ChatRoomMessageEntity>>(result);
      addChatRoomMessageAndKeyOffset(history.reversed.toList());
      refreshController.refreshCompleted();
    });
  }

  void onEnterSuggest(AssistantSuggestDataEntity data) {
    inputController.value = TextEditingValue(
        text: data.question,
        selection: TextSelection.collapsed(offset: data.question.length));
    onSend(data.id);
  }

  @override
  void onActionTo(String type, String text, data) {
    inputFocusNode.unfocus();
    print('type:$type , text:$text , data:$data');
    if (type == 'reply') {
      String replyText = data['reply_text'];
      addChatRoomMessageAndUpdate([obtainText(replyText)]);
    } else {
      if (isPlanExpired()) return;
      super.onActionTo(type, text, data);
    }
  }
}
